/*
 *       Copyright (c) 2008-2009 All rights reserved
 *               Asemantics S.r.l
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        Asemantics S.r.l."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Asemantics S.r.l.
 *    the Semantic Web company, Rome, London, Leiden and its contributors.
 *
 * 5. Neither the name of the company nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


package com.asemantics.poco.persistence;

import com.asemantics.poco.Entry;
import com.asemantics.poco.query.Query;

import javax.jdo.Extent;
import javax.jdo.FetchPlan;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;

/**
 * Default implementation of {@link com.asemantics.poco.persistence.Persistor},
 * based on <a href="http://java.sun.com/jdo/">JDO</a>.
 *
 * @author Michele Mostarda ( michele.mostarda@gmail.com )
 */
public class PersitorDefaultImpl implements Persistor {

    /**
     * The query language used by the persistence manager.
     */
    public static final String JDO_QUERY_LANGUAGE = "javax.jdo.query.JDOQL";

    /**
     * Internal logger.
     */
    private static final Logger LOGGER = Logger.getLogger( PersitorDefaultImpl.class );

    /**
     * Internal persistence manager.
     */
    private final PersistenceManagerFactory persistenceManagerFactory;

    /**
     * Internal persistence manager.
     */
    private PersistenceManager persistenceManager;

    /**
     * Constructor.
     */
    public PersitorDefaultImpl(Configuration configuration) {
        persistenceManagerFactory = JDOHelper.getPersistenceManagerFactory( configuration.getProperties() );
        persistenceManager = persistenceManagerFactory.getPersistenceManager();

        // The default behaviour is to fetch all objects in target hierarchy.
        FetchPlan fetchPlan = persistenceManager.getFetchPlan();
        fetchPlan.addGroup(FetchPlan.ALL);
    }

    /**
     * {@inheritDoc}
     */
    public <T,I> I getIdentifier(T obj) {
        return (I) persistenceManager.getObjectId(obj);
    }

    /**
     * {@inheritDoc}
     */
    public <T,I> I persist(T object) throws PersistorException {
        Transaction tx = persistenceManager.currentTransaction();
        try {
            tx.begin();

            T persisted = persistenceManager.makePersistent( object );

            if(tx.isActive()) {
                tx.commit();
            }

            Object objectId =  persistenceManager.getObjectId(persisted);
            return  (I) objectId;

        } catch (Exception e) {
            throw new PersistorException("Error while persisting object.", e);
        } finally {
            if ( tx.isActive() ) {
                tx.rollback();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public <T> List<T> retrieveAll(Class<T> clazz, boolean detach) throws PersistorException {
        checkManager();

        Transaction tx = persistenceManager.currentTransaction();
        try {
            tx.begin();

            Extent<T> extent = persistenceManager.getExtent(clazz, true);
            javax.jdo.Query query = persistenceManager.newQuery(extent);

            Collection<T> queryResult = (Collection<T>) query.execute();
            Collection<T> result = detach ? persistenceManager.detachCopyAll(queryResult) : queryResult;

            tx.commit();
            return result instanceof List ? (List<T>) result : new ArrayList<T>(result);
        } catch (Exception e) {
            throw new PersistorException("Error while retrieving object.", e);           
        }
        finally {
            if( tx.isActive() ) {
                tx.rollback();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public <T> List<T> retrieveAll(Class<T> clazz) throws PersistorException {
        return retrieveAll(clazz, true);
    }

    /**
     * {@inheritDoc}
     */
    public <T,I> T retrieve(I identifier) throws PersistorException {
        checkManager();

        Transaction tx = persistenceManager.currentTransaction();
        try {
            tx.begin();

            T result = (T) persistenceManager.getObjectById(identifier);
            result = persistenceManager.detachCopy(result);

            tx.commit();
            return result;
        } catch (Exception e) {
            throw new PersistorException("Error while retrieving object.", e);
        }
        finally {
            if( tx.isActive() ) {
                tx.rollback();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public <T,I> void delete(I identifier) throws PersistorException {
        checkManager();

        Transaction tx = persistenceManager.currentTransaction();
        try {
            tx.begin();

            T toBeDeleted = (T) persistenceManager.getObjectById(identifier);
            if(toBeDeleted == null) {
                throw new PersistorException("Cannot find object with identifier:" + identifier);
            }

            persistenceManager.deletePersistent(toBeDeleted);

            tx.commit();
        } catch (Exception e) {
            throw new PersistorException("Error while retrieving object.", e);
        }
        finally {
            if( tx.isActive() ) {
                tx.rollback();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public List<Entry> retrieveEntry(String userID, Query query, boolean detach)
    throws PersistorException {
        if(userID == null || query == null) {
            throw new IllegalArgumentException();
        }
        return processEntryQuery(userID, query, detach);
    }

    /**
     * {@inheritDoc}
     */
    public List<Entry> retrieveEntries(Query query, boolean detach)
    throws PersistorException {
        if(query == null) {
            throw new IllegalArgumentException();
        }
        return processEntryQuery(null, query, detach);
    }

    /**
     * {@inheritDoc}
     */
    public List<Entry> retrieveEntriesOf(String userID, Query query, boolean detach)
    throws PersistorException {
        if(userID == null || query == null) {
            throw new IllegalArgumentException();
        }
        return processEntriesOfQuery(userID, query, detach);
    }

    /**
     * {@inheritDoc}
     */
    public void open() {
        if(persistenceManager != null && ! persistenceManager.isClosed() ) {
            throw new IllegalStateException("Persitor already open.");
        }
        persistenceManager = persistenceManagerFactory.getPersistenceManager();
    }

    /**
     * {@inheritDoc}
     */
    public void close() {
        persistenceManager.close();
        persistenceManager = null;
    }
   
    /**
     * {@inheritDoc}
     */
    private void checkManager() {
        if( persistenceManager == null || persistenceManager.isClosed() ) {
            throw new IllegalStateException("Persistor is closed");
        }
    }

    /**
     * Processes the query parameters for entry retieval.
     * 
     * @param queryProfile the query profile to be executed.
     * @param detach
     * @return
     * @throws QueryAdapterException
     * @throws PersistorException
     */
    private  List<Entry> processQuery(QueryProfile queryProfile, boolean detach)
    throws QueryAdapterException, PersistorException {
        checkManager();

        LOGGER.debug("Executing JDOQL query: '" + queryProfile.getQuery() + "'");

        Transaction tx = persistenceManager.currentTransaction();

        try {
            tx.begin();

            javax.jdo.Query q = persistenceManager.newQuery(
                    JDO_QUERY_LANGUAGE,
                    queryProfile.getQuery()
            );
            Collection<Entry> queryResult = (Collection<Entry>) q.executeWithMap( queryProfile.getParameters() );

            Collection result = detach ? persistenceManager.detachCopyAll(queryResult) : queryResult;

            tx.commit();

            return result instanceof List ? (List<Entry>) result : new ArrayList<Entry>(result);

        } catch (Exception e) {
            throw new PersistorException("Error while retrieving object.", e);
        }
        finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }
    }

    /**
     * Processes the query parameters for entry retieval.
     *
     * @param userID
     * @param query
     * @param detach
     * @return
     * @throws QueryAdapterException
     * @throws PersistorException
     */
    private  List<Entry> processEntryQuery(String userID, Query query, boolean detach)
    throws PersistorException {
        try {
            QueryProfile queryProfile = new QueryAdapter().generateEntryQueryJDOQL(userID, query);
            return processQuery(queryProfile, detach);
        } catch (QueryAdapterException qae) {
            throw new PersistorException("Error while processing query.", qae);
        }
    }

    /**
     * Processes the query parameters for the retrieval of the entries that are friends of an entry.
     *
     * @param userID
     * @param query
     * @param detach
     * @return
     * @throws QueryAdapterException
     * @throws PersistorException
     */
    private  List<Entry> processEntriesOfQuery(String userID, Query query, boolean detach)
    throws PersistorException {
        try {
             QueryProfile queryProfile = new QueryAdapter().generateEntryQueryJDOQL(userID, query, true);
            return processQuery(queryProfile, detach);
        } catch (QueryAdapterException qae) {
            throw new PersistorException("Error while processing query.", qae);
        }
    }

}
